mod array;
mod boolean;
mod bulk_string;
mod double;
mod frame;
mod integer;
mod map;
mod null;
mod set;
mod simple_error;
mod simple_string;

pub use self::{
    array::RespArray, array::RespNullArray, bulk_string::BulkString,
    bulk_string::RespNullBulkString, frame::RespFrame, map::RespMap, null::RespNull, set::RespSet,
    simple_error::SimpleError, simple_string::SimpleString,
};
use bytes::{Buf, BytesMut};
use enum_dispatch::enum_dispatch;
use thiserror::Error;

const BUFFER_CAP: usize = 4096;

#[enum_dispatch]
pub trait RespEncode {
    fn encode(self) -> Vec<u8>;
}

#[enum_dispatch]
pub trait RespDecode: Sized {
    fn decode(buf: &mut BytesMut) -> Result<Self, RespError>;
}

#[derive(Error, Debug, PartialEq, Eq)]
pub enum RespError {
    #[error("Invalid frame: {0}")]
    InvalidFrame(String),
    #[error("Invalid frame type: {0}")]
    InvalidFrameType(String),
    #[error("Invalid frame length: {0}")]
    InvalidFrameLength(isize),
    #[error("Frame is not complete")]
    NotComplete,
    #[error("Parse error: {0}")]
    ParseIntError(#[from] std::num::ParseIntError),
    #[error("Parse utf8 error: {0}")]
    ParseUtf8Error(#[from] std::str::Utf8Error),
    #[error("Parse float error: {0}")]
    ParseFloatError(#[from] std::num::ParseFloatError),
}

const CRLF_LEN: usize = 2;

pub fn parse_length(buf: &mut BytesMut, prefix: &str) -> Result<usize, RespError> {
    let end = extract_simple_frame_data(buf, prefix, 1)?;
    let s = String::from_utf8_lossy(&buf[prefix.len()..end]);
    let len = s.parse()?;

    buf.advance(end + CRLF_LEN);

    Ok(len)
}

pub fn extract_simple_frame_data(
    buf: &BytesMut,
    prefix: &str,
    nth_crlf: usize,
) -> Result<usize, RespError> {
    if buf.len() < 3 {
        return Err(RespError::NotComplete);
    }

    if !buf.starts_with(prefix.as_bytes()) {
        return Err(RespError::InvalidFrameType(format!(
            "Expect: SimpleString(+) got: {:?}",
            buf
        )));
    }

    // search for "\r\n"
    let mut end = 0;
    let mut crlf_cnt = 0;
    for i in 0..buf.len() - 1 {
        if buf[i] == b'\r' && buf[i + 1] == b'\n' {
            crlf_cnt += 1;
            if crlf_cnt == nth_crlf {
                end = i;
                break;
            }
        }
    }

    if end == 0 {
        return Err(RespError::NotComplete);
    }

    Ok(end)
}

pub fn extract_fixed_data(
    buf: &BytesMut,
    expect: &str,
    expect_type: &str,
) -> Result<(), RespError> {
    if buf.len() < expect.len() {
        return Err(RespError::NotComplete);
    }

    if expect.as_bytes() == buf.as_ref() {
        Ok(())
    } else {
        Err(RespError::InvalidFrame(format!(
            "InvalidFrameType: expect is {}, got {}",
            expect_type,
            String::from_utf8_lossy(&buf[..])
        )))
    }
}
